<?php
/**
 * 日志处理类
 */
class Log {

    // 日志级别 从上到下，由低到高
    const EMERG     = 'EMERG';  // 严重错误: 导致系统崩溃无法使用
    const ALERT     = 'ALERT';  // 警戒性错误: 必须被立即修改的错误
    const CRIT      = 'CRIT';  // 临界值错误: 超过临界值的错误，例如一天24小时，而输入的是25小时这样
    const ERR       = 'ERR';  // 一般错误: 一般性错误
    const WARN      = 'WARN';  // 警告性错误: 需要发出警告的错误
    const NOTICE    = 'NOTIC';  // 通知: 程序可以运行但是还不够完美的错误
    const INFO      = 'INFO';  // 信息: 程序输出信息
    const DEBUG     = 'DEBUG';  // 调试: 调试信息
    const SQL       = 'SQL';  // SQL：SQL语句 注意只在调试模式开启时有效    

    // 日志信息
    static protected $log       =  array();
    
    // 日志存储函数
    static protected $write_function   =   null;
    
    //日志存储配置信息
    static protected $config  =   array(
        'log_time_format'   =>  ' c ',
        'log_file_size'     =>  2097152,
        'log_path'          =>  '',
    );

    // 日志初始化
    static public function init($config=array()){
        $type   =   (is_array($config) and isset($config['type']))?$config['type']:C('LOG_TYPE');
        self::$write_function = 'write_'.strtolower($type);
        // 设定错误和异常处理
        register_shutdown_function(array(__CLASS__, 'fatalError'));
        set_error_handler(array(__CLASS__, 'appError'));
        set_exception_handler(array(__CLASS__, 'appException'));
    }

    /**
     * 记录日志 并且会过滤未经设置的级别
     * @static
     * @access public
     * @param string $message 日志信息
     * @param string $level  日志级别
     * @param boolean $record  是否强制记录
     * @return void
     */
    static function record($message,$level=self::ERR,$record=false) {
        if($record || false !== strpos(C('LOG_LEVEL'),$level)) {
            self::$log[] =   "{$level}: {$message}\r\n";
        }
        //数据库记录
        elseif(strpos($_SERVER['REQUEST_URI'],'/DebugLog')===false and $level=='SQL' and S('SqlLog')){
            self::$log[] =   "{$level}:  {$message}\r\n{$_SERVER['REQUEST_URI']}\r\n\r\n";
        }
    }

    /**
     * 日志保存
     * @static
     * @access public
     * @param integer $type 日志记录方式
     * @param string $destination  写入目标
     * @return void
     */
    static function save($type='',$destination='') {
        if(empty(self::$log)) return ;

        if(empty($destination))
            $destination = Log::logDir().date('Ymd').'.log';
        if(!self::$write_function){
            $type = $type?$type:C('LOG_TYPE');
            self::$write_function = 'write_'.strtolower($type);            
        }
        $message    =   implode('',self::$log);
        $func = self::$write_function;
        self::$func($message,$destination);
        // 保存后清空日志缓存
        self::$log = array();
    }

    /**
     * 日志直接写入
     * @static
     * @access public
     * @param string $message 日志信息
     * @param string $level  日志级别
     * @param integer $type 日志记录方式
     * @param string $destination  写入目标
     * @return void
     */
    static function write($message,$level=self::ERR,$type='',$destination='') {
    if(!self::$write_function){
            $type = $type?$type:C('LOG_TYPE');
            self::$write_function = 'write_'.strtolower($type);            
        }
        if(empty($destination))
            $destination = Log::logDir().date('Ymd').'.log';    
        $func = self::$write_function;
        self::$func("{$level}: {$message}", $destination);
    }
    
    
    /**
     * 自定义异常处理
     * @access public
     * @param mixed $e 异常对象
     */
    public static function appException($e) {
        ob_start();
        $error = array();
        $error['message']   =   $e->getMessage();
        $trace              =   $e->getTrace();
        if('E'==$trace[0]['function']) {
            $error['file']  =   $trace[0]['file'];
            $error['line']  =   $trace[0]['line'];
        }else{
            $error['file']  =   $e->getFile();
            $error['line']  =   $e->getLine();
        }
        $error['trace']     =   $e->getTraceAsString();
        $errorStr = $error['message']."\r\n".$error['file']." 第".$error['line']."行.";
        Log::record($errorStr,Log::ERR);
        // 发送404信息
        header('HTTP/1.1 404 Not Found');
        header('Status:404 Not Found');
        halt($error);
    }
    
    /**
     * 自定义错误处理
     * @access public
     * @param int $errno 错误类型
     * @param string $errstr 错误信息
     * @param string $errfile 错误文件
     * @param int $errline 错误行数
     * @return void
     */
    public static function appError($errno, $errstr, $errfile, $errline) {
        if(error_reporting() === 0) return;// 加了@符号的错误将不再输出，也不会记录。
        switch ($errno) {
            case E_ERROR:
            case E_PARSE:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_USER_ERROR:
                $level = Log::ERR;
                break;
            case E_WARNING:
            case E_CORE_WARNING:
            case E_COMPILE_WARNING:
                $level = Log::WARN;
                break;
            case E_NOTICE:
            case E_STRICT:
                $level = Log::NOTICE;
                break;
            default:
                $level = Log::INFO;
                break;
        }
        if(C('system_log')){
            $errorStr = "[$errno] $errstr \r\n".$errfile." 第 $errline 行.";
            Log::record($errorStr,$level);
        }
        if(C('APP_DEBUG') OR $level==Log::ERR) {
            $error['message'] = $errstr;
            $error['file'] = $errfile;
            $error['line'] = $errline;
            halt($error);
        }else{
            $errorStr = "[$errno] $errstr ".$errfile." 第 $errline 行.";
            trace($errorStr,'','NOTIC');
        }
    }
    
    // 致命错误捕获
    public static function fatalError() {
        ob_start();
        Log::save();
        if ($e = error_get_last()) {
            switch($e['type']){
                case E_ERROR:
                case E_PARSE:
                case E_CORE_ERROR:
                case E_COMPILE_ERROR:
                case E_USER_ERROR:
                    ob_end_clean();
                    $errorStr = $e['message']."\r\n".$e['file']." 第".$e['line']."行.\r\n";
                    Log::write($errorStr,Log::ERR);
                    halt($e);
                    break;
            }
        }
    }
     
    
    /**
     * 显示错误追溯信息
     */
    public static function showDebugBackTrace($error)
    {
        if(C('APP_DEBUG')){
            $e = $error;
        }else{
            $e['message'] = '系统错误，请联系管理员！';
        }
        // 包含异常页面模板
        $exceptionFile =  PUB_DIR.'share/error.html';
        include $exceptionFile;
        exit;
    }
    
    public static function logDir(){
        return LOG_DIR.'debug/'.date('Ymd').'/';
    }
    
    
    /**
     * 日志写入函数(File)
     * @access public
     * @param string $log 日志信息
     * @param string $destination  写入目标
     * @return void
     */
    public static function write_file($log,$destination='') {
        $now = date(self::$config['log_time_format']);
        if(empty($destination))
            $destination = Log::logDir().date('Ymd').'.log';
        // 自动创建日志目录
        $log_dir = dirname($destination);
        if (!is_dir($log_dir)) {
            mkdir($log_dir, 0755, true);
        }
        //检测日志文件大小，超过配置大小则备份日志文件重新生成
        if(is_file($destination) && floor(self::$config['log_file_size']) <= filesize($destination) )
            rename($destination,dirname($destination).'/'.time().'-'.basename($destination));
        error_log("[{$now}] ".$_SERVER['REMOTE_ADDR'].' '.$_SERVER['REQUEST_URI']."\r\n{$log}\r\n", 3,$destination);
    }
    
    
    /**
     * 日志写入函数(Sae)
     * @access public
     * @param string $log 日志信息
     * @param string $destination  写入目标
     * @return void
     */
    public static function write_sae($log,$destination='') {
        static $is_debug=null;
        $now = date(self::$config['log_time_format']);
        $logstr="[{$now}] ".$_SERVER['REMOTE_ADDR'].' '.$_SERVER['REQUEST_URI']."\r\n{$log}\r\n";
        if(is_null($is_debug)){
            preg_replace('@(\w+)\=([^;]*)@e', '$appSettings[\'\\1\']="\\2";', $_SERVER['HTTP_APPCOOKIE']);
            $is_debug = in_array($_SERVER['HTTP_APPVERSION'], explode(',', $appSettings['debug'])) ? true : false;
        }
        if($is_debug)
            sae_set_display_errors(false);//记录日志不将日志打印出来
        sae_debug($logstr);
        if($is_debug)
            sae_set_display_errors(true);
    
    }
}